Ten dokument opisuje sposoby, możliwości i techniki porządkowania i czyszczenia danych z użyciem biblioteki (Pandas) i (Polars) python. <br/ Mam nadzieję, że będzie to dla Ciebie przydatne! Miłej lektury!
Author
Piotr Dłubak
Published
February 15, 2025
Cel analizy
Badana populacja: klienci sklepów spożywczych
Rodzaj badania: ankieta on-line dostępna dla wszystkich użytkowników na terenie Polski
Metoda analizy: EDA (“Exploratory Data Analysis”) - analiza deskrypcyjna
Analiza deskrypcyjna to proces badania, opisywania i interpretacji danych w celu uzyskania wglądu i zrozumienia ich cech, wzorców i związków. Jest to technika często stosowana w dziedzinach naukowych, badań społecznych, statystyce, lingwistyce i wielu innych dziedzinach.
Głównym celem analizy deskrypcyjnej jest opisanie i podsumowanie danych w sposób, który ujawnia istotne informacje. Może obejmować takie elementy jak obliczanie średnich, median, odchyleń standardowych, minimalnych i maksymalnych wartości, oraz prezentowanie danych w postaci tabel, wykresów lub grafów.
Analiza deskrypcyjna umożliwia identyfikację kluczowych cech, trendów, anomalii i relacji w danych. Może również pomóc w odkrywaniu wzorców, porównywaniu grup lub kategorii danych oraz wyprowadzaniu wniosków na podstawie zebranych informacji.
Głównym celem EDA jest zapewnienie wglądu w dane jeszcze przed sformułowaniem jakichkolwiek założeń. Pomaga identyfikować oczywiste błędy, lepiej pojmować wzorce występujące w obrębie danych, wykrywać wartości odstające i anomalie, a także odnajdywać interesujące relacje między zmiennymi.
Po przeprowadzeniu analizy EDA i uzyskaniu istotnych spostrzeżeń wciąż można wykorzystać tę metodę do bardziej zaawansowanej analizy danych lub modelowania, w tym na potrzeby uczenia maszynowego.
Wyniki przeprowadzonej analizy pokazały, że dla współczesnego konsumenta decyzja o zakupie produktu nie jest motywowana wyłącznie chęcią zaspokojenia określonych potrzeb, ale w znacznym stopniu determinuje ją potrzeba demonstracji przekonań, statusu społeczno-ekonomicznego i stylu życia. Nowoczesny model konsumpcji niesie określone konsekwencje dla praktyki działań marketingowych.
Badanie ankietowe dotyczyło postaw i zachowań respondentów w tracie dokonywania zakupów produktów żywnościowych. W tym celu został skonstruowany kwestionariusz ankiety
Cechy, takie jak wiek, płeć, wykształcenie, preferowany typ sklepu, dochody netto, preferowana marka sklepu, preferowany towar, preferowany rodzaj promocji oraz czynnik zakupowy, mogą wpływać na wysokość zakupów żywnościowych.
Wymagania dotyczące danych: Zdefiniowenie zakresu analizy (zmienne, obserwacje, zakresy dziedzinowe)
#### Wymagania dotyczące danych1. **Zmienne ilościowe:** - wiek: 13-105 lat - liczba osób w rodzinie: 1-20 - dochody: >0 - wydatki: >02. **Zmienne jakościowe:** - płeć: "mężczyzna", "kobieta" - wykształcenie: "podstawowe", "zawodowe", "średnie", "wyższe" - preferowany typ sklepu: "bazarek", "osiedlowy", "supermarket", "galeria" - preferowana marka sklepu: dowolne - preferowany towar: dowolne - czynnik zakupowy: dowolne - rodzaj promocji: "sugestia kasjera", "gazetka", "reklama RTV", "sms", "e-mail", "karta", "aplikacja", "nie korzystam" - miasto: dowolne
import warningsimport pandas as pdimport numpy as npimport seaborn as snsimport matplotlib.pyplot as pltimport statsmodels.api as smimport randomfrom scipy.stats import zscoreimport reimport mathwarnings.filterwarnings("ignore")from itables import init_notebook_mode, showimport itables.options as opt# opt.layout = {# "topStart": "pageLength",# "topEnd": "search",# "bottomStart": "info",# "bottomEnd": "paging"# } # (default value)
Note
Note that there are five types of callouts, including: note, tip, warning, caution, and important.
Tip
This is an example of an tip callout
Caution
This is an example of an caution callout
Warning
This is an example of an warning callout
Important
This is an example of an important callout
Remember to complete this section.
This information is crucial for understanding the concept.
You did a great job completing this task.
I. Wstępne przetwarzanie
1. Przygotowanie danych
1.1. Pozyskanie danych
Dane ankietowe do analizy pozyskano od klientow sklepów spożywczych w formie ankiety on-line udostępnionej dla wszystkich użytkowników na terenie Polski.
Informacje o danych demograficznych pozyskano z lokalnych baz danych organizatora badań.
Pozyskane dane zostały przekazane do organizatora celem zaladownia danych do plików.
W ten sposób do analizy otrzymano komplet 4 plików:
Plik
1
‘ankieta_01a.xlsx’
2
‘ankieta_01b.xlsx’
3
‘ankieta_02.csv’
4
‘miasta.json’
pliki te stanowią źródla informacji dla prowadzonej analizy EDA.
2.1.Określenie zasad uporządkowanego zbioru danych
Zasady Danych uporządkowanych: 1. Każda zmienna tworzy kolumnę. 2. Każda obserwacja tworzy wiersz. 3. Każdy rodzaj jednostki tworzy tabelę.
Dzięki uporządkowaniu danych: * łatwiej skuteczniej można przekształcać, zmieniać, Modelować, Wizualizować dane. * Uporządkowane dane zapewniają ustandaryzowany sposób. Łączenia struktury zestawu danych, jego fizyczny układ z jego semantyką. Jego znaczeniem. Struktura danych ramki danych składa się z wierszy i kolumn. * Kolumny oznaczają etykiety nazwy zmiennej * Wiersze zawierają daną. Opisywaną jednostkę obserwowaną.
Semantyka danych. * Zbiór danych to zbiór wartości. * Liczby w przypadku zmiennych ilościowych. * Ciągi znaków w przypadku zmiennych jakościowych. * Każda wartość należy do zmiennej i obserwacji. * Zmienna zawiera wszystkie wartości.
2.2. Identyfikacja czy posiadane dane posiadają oznaki danych nieuporządkowych tj. są niezgodne z paradygmatem “TIDY DATA”
2.2.1. Niezgodność nr 1. Duplikaty
Sprawdzenie czy w danych występują duplikaty
Code
print(f"Nazwa pliku: --> ankieta_01a duplikaty w pliku:--> {ankieta_01a.duplicated().any()}")print(f"Nazwa pliku: --> ankieta_01b duplikaty w pliku:--> {ankieta_01b.duplicated().any()}")print(f"Nazwa pliku: --> ankieta_02 duplikaty w pliku:--> {ankieta_02.duplicated().any()}")print(f"Nazwa pliku: --> miasta duplikaty w pliku:--> {miasta.duplicated().any()}")
Nazwa pliku: --> ankieta_01a duplikaty w pliku:--> True
Nazwa pliku: --> ankieta_01b duplikaty w pliku:--> False
Nazwa pliku: --> ankieta_02 duplikaty w pliku:--> False
Nazwa pliku: --> miasta duplikaty w pliku:--> False
Code
display(ankieta_01a[ankieta_01a.duplicated()])
nr_respondenta
kod_miasta
wiek|liczba osób w rodzinie
m_wykształcenie
k_wykształcenie
PREFEROWANY TYP SKLEPU
preferowana marka sklepu
preferowanay towar
preferowany rodzaj promocji
preferowany rodzaj promocji.1
czynnik zakupowy
zakupy ile razy w mc
8
R_059
M_016
26|5
NaN
średnie
SUPERMARKET
ALDI
Mięso i wędliny
sugestia
kasjera
cena
od 5 do 10
34
R_058
M_052
47|4
NaN
średnie
SUPERMARKET
ŻABKA
Produkty mleczne
gazetka
NaN
marka
od 11 do 15
50
R_057
M_054
69|2
NaN
średnie
OSIEDLOWY
LIDL
Produkty piekarnicze
gazetka
NaN
cena
od 11 do 15
Code
#Usunięcie zduplikowanych danychprint(f'UWAGA: Usunięto wiersze ({sum(ankieta_01a.duplicated())}) z duplikującymi się danymi')ankieta_01a.drop_duplicates(inplace=True)print(f'duplikaty w pliku:--> {ankieta_01a.duplicated().any()}')
UWAGA: Usunięto wiersze (3) z duplikującymi się danymi
duplikaty w pliku:--> False
2.2.2. Niezgodność nr 2. Podzielone obserwacje pomiędzy wiele tabel
Code
# dane z ankiety ankieta_01 zostały pozielona na dwa pliki:ankieta_01a i ankieta_01b# Przed połączeniem należy sprawdzić zgodnośc tabel w zakresie kolumniflist(ankieta_01a.columns) ==list(ankieta_01a.columns):print("Kolumny są zgodne.")else:print("Kolumny nie są zgodne.")
Kolumny są zgodne.
Code
#Połącznie plików: ankieta_01a i ankieta_01b w jeden plikankieta_01 = pd.concat([ankieta_01a, ankieta_01b], axis=0)ankieta_01.head(2)
Table 1: Astronomical object
nr_respondenta
kod_miasta
wiek|liczba osób w rodzinie
m_wykształcenie
k_wykształcenie
PREFEROWANY TYP SKLEPU
preferowana marka sklepu
preferowanay towar
preferowany rodzaj promocji
preferowany rodzaj promocji.1
czynnik zakupowy
zakupy ile razy w mc
0
R_005
M_057
38|5
zawodowe
NaN
OSIEDLOWY
LIDL
Mięso i wędliny
gazetka
NaN
jakość
od 1 do 4
1
R_010
M_053
59|1
zawodowe
NaN
OSIEDLOWY
NETTO
Mięso i wędliny
sms
NaN
marka
od 1 do 4
2.2.3. Niezgodność nr 3. Przechowywanie w rożnych wierszach jednej kolumny wartosci wielu zmiennych
Code
ankieta_02.head(2)
Table 2: wjednej kolumnie “miara” przechowywane są nazwy 2 różnych zmiennych a w kolumnie “wartość” wartosci odpowiadające różnym zmiennych
2.2.5. Niezgodność nr 5. Podzielone wartości jednej zmiennej pomiędzy kilka kolumn
Code
#Zmienna preferowany rodzaj promocji została podzielona na dwie kolumnyankieta_01[['preferowany rodzaj promocji','preferowany rodzaj promocji.1']].head(2)
preferowany rodzaj promocji
preferowany rodzaj promocji.1
0
gazetka
NaN
1
sms
NaN
Code
ankieta_01['preferowany rodzaj promocji.1']=ankieta_01['preferowany rodzaj promocji.1'].fillna('_')ankieta_01['rodzaj promocji'] = ankieta_01['preferowany rodzaj promocji'].astype(str).str.cat(ankieta_01['preferowany rodzaj promocji.1'].astype(str), sep=' ')ankieta_01['rodzaj promocji'] = ankieta_01['rodzaj promocji'].str.replace('_','') # usunieto zbędne znaki_ankieta_01= ankieta_01.drop('preferowany rodzaj promocji', axis=1) # usunięcie zbędnej kolumnyankieta_01= ankieta_01.drop('preferowany rodzaj promocji.1', axis=1) # usunięcie zbędnej kolumny
2.2.6. Niezgodność nr 6. Przechowywanie w jednej kolumnie połączonych wartosci wielu zmiennych.
Code
ankieta_01[['wiek|liczba osób w rodzinie']].head(3)
wiek|liczba osób w rodzinie
0
38|5
1
59|1
2
35|1
Code
ankieta_01[['wiek', 'liczba osób w rodzinie']] = ankieta_01['wiek|liczba osób w rodzinie'].str.split('|', expand=True) # podzielenie kolumnyankieta_01= ankieta_01.drop('wiek|liczba osób w rodzinie', axis=1) # usunięcie zbędnej kolumny
2.2.7. Niezgodność nr 7. Zmienne podzielonone pomiędzy wiele tabel
nowe_nazwy = { ('wartość', 'dochody roczne'):'dochody',('wartość', 'wydatki_na_żywność_mc'):'wydatki',('nr_respondenta', '') :'nr_respondenta'}# Zmiana nazw kolumn na podstawie słownikaankieta_02 = ankieta_02.rename(columns=nowe_nazwy)
Code
ankieta_01= pd.merge(ankieta_01,ankieta_02, on ='nr_respondenta')ankieta_01.head(2)
nr_respondenta
kod_miasta
PREFEROWANY TYP SKLEPU
preferowana marka sklepu
preferowanay towar
czynnik zakupowy
zakupy ile razy w mc
płeć
wykształcenie
rodzaj promocji
wiek
liczba osób w rodzinie
Miasto
Liczba ludności
dochody
wydatki
0
R_005
M_057
OSIEDLOWY
LIDL
Mięso i wędliny
jakość
od 1 do 4
m
zawodowe
gazetka
38
5
Ostrowiec Świętokrzyski
69715
28
750
1
R_010
M_053
OSIEDLOWY
NETTO
Mięso i wędliny
marka
od 1 do 4
m
zawodowe
sms
59
1
Lubin
78937
30
590
Code
dane=ankieta_01.copy()baza = dane.copy()
3.Czyszczenie danych
3.1 Wprowadzenie
Zdefiniować, czym są czyste dane:
Dane musza być dokładne.
Kompletne.
Spójne.
Ważne.
Aktualne.
Bez duplikatów.
Jednolite.
Rozpoznać „brudne” dane
Mozliwe oznaki zanieczyszczonych danych
a) Różna pisownia wariantów zmiennej kategorycznej płeć.
b) Zbędne myślniki.
c) Litery drukowane
d) Wartości niezgodne z rzeczywistością
e) Zbędne odstępy.
f) Czeskie błędy.
g) Dane niezgodne z wiedzą dziedzinową.
h) Dane niezgodne ze zdrowym rozsądkiem.
i) Wartości liczbowe we wartościach zmiennej kategorycznej, gdzie powinna być wartość? Tekstowa.
j) Wartości tekstowe w zmiennych ilościowych.
k) Duplikaty.
l) Myślniki zamiast wartości.
m) Zera zamiast wartości.
n) Wartości brakujące-puste.
o) Różne jednostki jednej zmiennej.
Odpowiedzią na ww. nieprawidłowości będą odpowiednie oczyszczenie danych z błędów.
3.2. Błędne dane
3.2.1. Czyszczenie i uporządkowanie nazw kolumn
Code
baza.columns = baza.columns.str.lower().str.replace("_"," ")kolejnosc = ['nr respondenta', 'płeć', 'wykształcenie', 'wiek','liczba osób w rodzinie','preferowany typ sklepu', 'preferowana marka sklepu','preferowanay towar', 'czynnik zakupowy', 'zakupy ile razy w mc','rodzaj promocji', 'miasto','liczba ludności','dochody', 'wydatki']baza= baza[kolejnosc]
3.2.2. Błędy - zmienna tekstowa
Code
zmienna_tekstowa= ['płeć', 'wykształcenie','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy','rodzaj promocji']#Zamiana drukowanych liter na małebaza[zmienna_tekstowa]=baza[zmienna_tekstowa].applymap(lambda x: x.lower() ifisinstance(x, str) else x)zmienna_liczbowa = [ 'wiek','liczba osób w rodzinie','liczba ludności','dochody', 'wydatki', 'od', 'do', 'średnia częstość zakupów w tyg']# wyszukanie niezozwolonych znaków #znaki= [[0-9]|\s|_|/.|,|\.|\?|:|;|>|<|\+|=|{|}|-|<|>| !"#$%&'()*+,-./:;<=>?@[\]^_{|}~] ('^[A-Z]+$|\W|_|\d',znaki = (r'[A-Z]|[0-9]|_|,|W|\.|\?|-')print(f'Dane, które zawierają niedozwolone znaki: {znaki}')print('='*60)for zmienna in zmienna_tekstowa: czy_inne_znaki = baza[zmienna].str.contains(znaki, na=False)ifany(czy_inne_znaki) ==True: display(baza.loc[czy_inne_znaki, ['nr respondenta',zmienna]])
Dane, które zawierają niedozwolone znaki: [A-Z]|[0-9]|_|,|W|\.|\?|-
============================================================
nr respondenta
wykształcenie
48
R_003
podstawowe.
58
R_053
śred5nie
113
R_095
wyższe,
nr respondenta
rodzaj promocji
9
R_012
e-mail
50
R_026
e-mail
51
R_045
e-mail
Code
# poprawaznaki = (r'[A-Z]|[0-9]|_|,|W|\.|\?|-')zmienna_tekstowa= ['płeć', 'wykształcenie','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy','rodzaj promocji']for zmienna in zmienna_tekstowa: czy_inne_znaki = baza[zmienna].str.contains(znaki, na=False)ifany(czy_inne_znaki) ==True: baza[zmienna] = baza[zmienna].str.replace(znaki,"")
Code
baza['preferowany typ sklepu'].value_counts()
preferowany typ sklepu
supermarket 34
osiedlowy 30
galeria 28
bazarek 27
super 2
bazar 2
Name: count, dtype: int64
3.2.3. Niezgodności ref. - zmienna tekstowa
Code
płeć_ref = ['m', 'k']preferowany_typ_sklepu_ref = ['bazarek', 'osiedlowy', 'supermarket', 'galeria']wykształcenie_ref = ['podstawowe', 'zawodowe', 'średnie', 'wyższe']niepasujące_dane_kolumna1 = baza.loc[~baza['płeć'].isin(płeć_ref)]niepasujące_dane_kolumna2 = baza.loc[~baza['preferowany typ sklepu'].isin(preferowany_typ_sklepu_ref)]niepasujące_dane_kolumna3 = baza.loc[~baza['wykształcenie'].isin(wykształcenie_ref)]ifnot niepasujące_dane_kolumna1.empty:print("Niepasujące dane w kolumnie 'płeć':") display(niepasujące_dane_kolumna1[['płeć']])ifnot niepasujące_dane_kolumna2.empty:print("Niepasujące dane w kolumnie 'preferowany typ sklepu':") display(niepasujące_dane_kolumna2[['preferowany typ sklepu']])ifnot niepasujące_dane_kolumna3.empty:print("Niepasujące dane w kolumnie 'wykształcenie':") display(niepasujące_dane_kolumna3[['wykształcenie']])
Niepasujące dane w kolumnie 'preferowany typ sklepu':
preferowany typ sklepu
86
super
91
super
97
bazar
108
NaN
118
bazar
120
NaN
Niepasujące dane w kolumnie 'wykształcenie':
wykształcenie
48
podstawowe.
49
średn
58
śred5nie
93
w
113
wyższe,
116
ś
3.2.3. Obsługa błędów w zmiennych tekstowych
Code
dict_wyk={'średn':'średnie', 'ś': 'średnie', 'w':'wyższe'}baza['wykształcenie'] = baza['wykształcenie'].replace(dict_wyk)preferowany_typ_sklepu_ref = {'super': 'supermarket', 'bazar':'bazarek'}baza['preferowany typ sklepu'] = baza['preferowany typ sklepu'].replace(preferowany_typ_sklepu_ref)
Code
baza['preferowana marka sklepu'] = baza['preferowana marka sklepu'].str.title()
Code
custom_order = ['podstawowe', 'zawodowe','średnie', 'wyższe']cat_dtype = pd.CategoricalDtype(categories=custom_order, ordered=True)# Zmiana typu kolumny na "categorical" z zdefiniowanym porządkiembaza['wykształcenie'] = baza['wykształcenie'].astype(cat_dtype)zmienna_tekstowa= ['płeć','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy','rodzaj promocji']baza[zmienna_tekstowa]=baza[zmienna_tekstowa].astype('category')
# Podzielenie wartości liczbowych baza[['od', 'do']] = baza['zakupy ile razy w mc'].str.extract(r'(\d+\.\d+|\d+)[^\d]*(\d+\.\d+|\d+)')baza['średnia częstość zakupów w tyg'] = (baza['od'].astype(float) + baza['do'].astype(float)) /2baza['średnia częstość zakupów w tyg'] = baza['średnia częstość zakupów w tyg'].apply(math.ceil)display(baza[['od', 'do']].head())baza[['nr respondenta', 'od', 'do', 'średnia częstość zakupów w tyg']]baza.drop(['od', 'do', 'zakupy ile razy w mc'], axis=1, inplace=True)baza.head(3)
od
do
0
1
4
1
1
4
2
15
22
3
1
4
4
5
10
nr respondenta
płeć
wykształcenie
wiek
liczba osób w rodzinie
preferowany typ sklepu
preferowana marka sklepu
preferowanay towar
czynnik zakupowy
rodzaj promocji
miasto
liczba ludności
dochody
wydatki
średnia częstość zakupów w tyg
0
R_005
m
zawodowe
38
5
osiedlowy
Lidl
mięso i wędliny
jakość
gazetka
Ostrowiec Świętokrzyski
69715
28
750
3
1
R_010
m
zawodowe
59
1
osiedlowy
Netto
mięso i wędliny
marka
sms
Lubin
78937
30
590
3
2
R_036
m
wyższe
35
1
galeria
Żabka
mięso i wędliny
jakość
gazetka
Biała Podlaska
57957
35
753
19
3.2.4. Błędy - zmienna liczbowa
Code
zmienna_liczbowa = ['wiek', 'liczba osób w rodzinie', 'dochody', 'wydatki', 'średnia częstość zakupów w tyg']for kolumna in zmienna_liczbowa: czy_inne_znaki = baza[kolumna].astype(str).str.contains('\D')print(f'"{kolumna}" zawiera znaki nie liczbowe : --> {any(czy_inne_znaki)}')ifany(czy_inne_znaki): display(baza.loc[czy_inne_znaki, ['nr respondenta', kolumna]])
"wiek" zawiera znaki nie liczbowe : --> True
nr respondenta
wiek
9
R_012
33 lata
40
R_004
67 lat
"liczba osób w rodzinie" zawiera znaki nie liczbowe : --> False
"dochody" zawiera znaki nie liczbowe : --> True
nr respondenta
dochody
24
R_011
-31
52
R_007
29 zł
"wydatki" zawiera znaki nie liczbowe : --> True
nr respondenta
wydatki
23
R_006
-556
28
R_021
669 PLN
"średnia częstość zakupów w tyg" zawiera znaki nie liczbowe : --> False
Code
#pOPRAWA # # Numer indeksów, dla których chcemy zmienić wartości na NaN# indexes_to_replace = [10,5,34,35,77,103,1]# for index in indexes_to_replace:# baza.at[index, 'dochód'] = np.nandef konwertuj_na_int(wartosc):returnint(wartosc.replace(' zł', '').replace(' PLN', '').replace(' ', ''))# Konwersja wartości w kolumnie 'dochody' na intbaza['dochody'] = baza['dochody'].apply(konwertuj_na_int)# Konwersja wartości w kolumnie 'wydatki' na intbaza['wydatki'] = baza['wydatki'].apply(konwertuj_na_int)
Code
# Konwersja zmiennych z 'object' na 'int64'baza['wiek'] = pd.to_numeric(baza['wiek'], errors='coerce').astype('float')baza['liczba osób w rodzinie'] = pd.to_numeric(baza['liczba osób w rodzinie'], errors='coerce').astype('float')print(baza.dtypes)baza['dochody']=baza['dochody']*1000baza['wydatki']=baza['wydatki']*12
nr respondenta object
płeć category
wykształcenie category
wiek float64
liczba osób w rodzinie float64
preferowany typ sklepu category
preferowana marka sklepu category
preferowanay towar category
czynnik zakupowy category
rodzaj promocji category
miasto object
liczba ludności int64
dochody int64
wydatki int64
średnia częstość zakupów w tyg int64
dtype: object
3.2.5. Niezgodności ref. - zmienna liczbowa
Code
#dozwolone przedziały i wartosci# 'wiek', 18 -105# 'liczba osób w rodzinie', 1-10# 'dochody', [> 0]# 'wydatki', [> 0 ]# dochody > wydatkiimport pandas as pd# Sprawdzanie niezgodności w zmiennej 'dochody'czy_ujemne = (baza['dochody'] <0)czy_zero = (baza['dochody'] ==0)ifany(czy_ujemne) orany(czy_zero):print(f'Zmienna "dochody" zawiera wartości < 0 lub = 0.') display(baza.loc[czy_ujemne | czy_zero, ['nr respondenta', 'dochody']])else:print('Brak niezgodności w zmiennej "dochody".')# Sprawdzanie niezgodności w zmiennej 'wydatki'czy_ujemne = (baza['wydatki'] <0)ifany(czy_ujemne):print(f'Zmienna "wydatki" zawiera wartości < 0.') display(baza.loc[czy_ujemne, ['nr respondenta', 'wydatki']])else:print('Brak niezgodności w zmiennej "wydatki".')# Sprawdzanie niezgodności między 'wydatki' a 'dochody'czy_wieksze_wydatki = baza['wydatki'] > baza['dochody']ifany(czy_wieksze_wydatki):print('Istnieją wydatki większe od dochodów.') display(baza.loc[czy_wieksze_wydatki, ['nr respondenta', 'dochody', 'wydatki']])else:print('Brak niezgodności między wydatkami a dochodami.')# Sprawdzanie niezgodności w zmiennej 'wiek'czy_poza_zakresem = (baza['wiek'] <15) | (baza['wiek'] >105)ifany(czy_poza_zakresem):print('Zmienna "wiek" zawiera wartości spoza zakresu 15-105.') display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'wiek']])else:print('Brak niezgodności w zmiennej "wiek".')# Sprawdzanie niezgodności w zmiennej 'liczba osób w rodzinie'czy_poza_zakresem = (baza['liczba osób w rodzinie'] <1) | (baza['liczba osób w rodzinie'] >10)ifany(czy_poza_zakresem):print('Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.') display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'liczba osób w rodzinie']])else:print('Brak niezgodności w zmiennej "liczba osób w rodzinie".')
Zmienna "dochody" zawiera wartości < 0 lub = 0.
nr respondenta
dochody
23
R_006
0
24
R_011
-31000
82
R_104
0
109
R_078
0
Zmienna "wydatki" zawiera wartości < 0.
nr respondenta
wydatki
23
R_006
-6672
Istnieją wydatki większe od dochodów.
nr respondenta
dochody
wydatki
22
R_002
26000
27600
24
R_011
-31000
7092
82
R_104
0
12024
109
R_078
0
10692
Zmienna "wiek" zawiera wartości spoza zakresu 15-105.
nr respondenta
wiek
52
R_007
158.0
Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.
nr respondenta
liczba osób w rodzinie
13
R_040
30.0
3.2.6. Obsługa błędów w zmiennych liczbowych
Z tych danych wynika, że są to błędne wpisy. Z uwagi na niewielką ilość pozyskanych danych usuwanie takich danych znacznie zmniejszyło by liczbę dostapych danych, dlatego zostanie dokonana zamiana błędych wartosci na wartości brakujące, następnie w dalszych krokach braki zostaną obsłużone.
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [23,24,82,109]for index in indexes_to_replace: baza.at[index, 'dochody'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [23]for index in indexes_to_replace: baza.at[index, 'wydatki'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [52]for index in indexes_to_replace: baza.at[index, 'wiek'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [13]for index in indexes_to_replace: baza.at[index, 'liczba osób w rodzinie'] = np.nan
Code
# # Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [52,22]for index in indexes_to_replace: baza.at[index, 'wydatki'] = np.nan
Code
import pandas as pd# Sprawdzanie niezgodności w zmiennej 'dochody'czy_ujemne = (baza['dochody'] <0)czy_zero = (baza['dochody'] ==0)ifany(czy_ujemne) orany(czy_zero):print(f'Zmienna "dochody" zawiera wartości < 0 lub = 0.') display(baza.loc[czy_ujemne | czy_zero, ['nr respondenta', 'dochody']])else:print('Brak niezgodności w zmiennej "dochody".')# Sprawdzanie niezgodności w zmiennej 'wydatki'czy_ujemne = (baza['wydatki'] <0)ifany(czy_ujemne):print(f'Zmienna "wydatki" zawiera wartości < 0.') display(baza.loc[czy_ujemne, ['nr respondenta', 'wydatki']])else:print('Brak niezgodności w zmiennej "wydatki".')# Sprawdzanie niezgodności między 'wydatki' a 'dochody'czy_wieksze_wydatki = baza['wydatki'] > baza['dochody']ifany(czy_wieksze_wydatki):print('Istnieją wydatki większe od dochodów.') display(baza.loc[czy_wieksze_wydatki, ['nr respondenta', 'dochody', 'wydatki']])else:print('Brak niezgodności między wydatkami a dochodami.')# Sprawdzanie niezgodności w zmiennej 'wiek'czy_poza_zakresem = (baza['wiek'] <15) | (baza['wiek'] >105)ifany(czy_poza_zakresem):print('Zmienna "wiek" zawiera wartości spoza zakresu 15-105.') display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'wiek']])else:print('Brak niezgodności w zmiennej "wiek".')# Sprawdzanie niezgodności w zmiennej 'liczba osób w rodzinie'czy_poza_zakresem = (baza['liczba osób w rodzinie'] <1) | (baza['liczba osób w rodzinie'] >10)ifany(czy_poza_zakresem):print('Zmienna "liczba osób w rodzinie" zawiera wartości spoza zakresu 1-10.') display(baza.loc[czy_poza_zakresem, ['nr respondenta', 'liczba osób w rodzinie']])else:print('Brak niezgodności w zmiennej "liczba osób w rodzinie".')
Brak niezgodności w zmiennej "dochody".
Brak niezgodności w zmiennej "wydatki".
Brak niezgodności między wydatkami a dochodami.
Brak niezgodności w zmiennej "wiek".
Brak niezgodności w zmiennej "liczba osób w rodzinie".
3.3. Brakujące dane
3.3.1 Sprawdzenie czy w danych wystepują braki (Null, None) [razem, w wierszach , w kolummnach]
Code
def braki_sprawdzenie(dane):""" Analizuje brakujące dane w tabeli. Parametry: dane (DataFrame): Ramka danych do analizy. Zwraca: None """ liczba = dane.isnull().sum().sum() proc = (liczba / (dane.shape[0]*dane.shape[1])*100).round(2)if liczba ==0:print('Analiza brakujących danych:')print('='*45)print('W tabeli nie stwierdzono brakujących danych!')else:print('Analiza brakujących danych:')print('='*45)print(f'Liczba brakujących danych w tabeli: {liczba}')print(f'Procent brakujących danych w tabeli: {proc}%')print('='*45) rows_with_missing_data = dane[dane.isnull().any(axis=1)] brakujace_dane = rows_with_missing_data.isnull().sum(axis=0) udzial_brakujacych_danych = ((rows_with_missing_data.isnull().sum(axis=0) / dane.shape[0])*100).round(1) wyniki = pd.DataFrame({'liczba': brakujace_dane, 'proc': udzial_brakujacych_danych})print('Brakujące dane w zmiennych (kolumny):') display(wyniki) rows_with_missing_data = dane[dane.isnull().any(axis=1)] brakujace_dane = rows_with_missing_data.isnull().sum(axis=1) udzial_brakujacych_danych = (rows_with_missing_data.isnull().sum(axis=1) / dane.shape[1]*100).round(1) wyniki = pd.DataFrame({'liczba': brakujace_dane, 'proc': udzial_brakujacych_danych})print('='*45)print('Brakujące dane w obserwacjach (wiersze):') display(wyniki)print('='*45) fig, ax = plt.subplots(figsize=(9.5, 4)) sns.heatmap(dane.isnull(), cmap='coolwarm', ax=ax) rows_with_missing_data = dane[dane.isnull().any(axis=1)]print('Tabela z brakującymi danymi:') display(rows_with_missing_data)braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
Liczba brakujących danych w tabeli: 18
Procent brakujących danych w tabeli: 0.96%
=============================================
Brakujące dane w zmiennych (kolumny):
liczba
proc
nr respondenta
0
0.0
płeć
0
0.0
wykształcenie
3
2.4
wiek
3
2.4
liczba osób w rodzinie
1
0.8
preferowany typ sklepu
2
1.6
preferowana marka sklepu
2
1.6
preferowanay towar
0
0.0
czynnik zakupowy
0
0.0
rodzaj promocji
0
0.0
miasto
0
0.0
liczba ludności
0
0.0
dochody
4
3.2
wydatki
3
2.4
średnia częstość zakupów w tyg
0
0.0
=============================================
Brakujące dane w obserwacjach (wiersze):
liczba
proc
9
1
6.7
13
1
6.7
22
1
6.7
23
2
13.3
24
1
6.7
40
1
6.7
48
1
6.7
52
2
13.3
58
1
6.7
82
1
6.7
108
2
13.3
109
1
6.7
113
1
6.7
120
2
13.3
=============================================
Tabela z brakującymi danymi:
nr respondenta
płeć
wykształcenie
wiek
liczba osób w rodzinie
preferowany typ sklepu
preferowana marka sklepu
preferowanay towar
czynnik zakupowy
rodzaj promocji
miasto
liczba ludności
dochody
wydatki
średnia częstość zakupów w tyg
9
R_012
m
zawodowe
NaN
5.0
bazarek
Biedronka
napoje
marka
e-mail
Gorzów Wielkopolski
124581
30000.0
9000.0
3
13
R_040
m
wyższe
69.0
NaN
osiedlowy
Biedronka
napoje
marka
reklama rtv
Piotrków Trybunalski
76279
38000.0
9144.0
13
22
R_002
m
podstawowe
16.0
1.0
bazarek
Netto
owoce i warzywa
dostępność
gazetka
Suwałki
69442
26000.0
NaN
3
23
R_006
m
zawodowe
28.0
1.0
bazarek
Dino
owoce i warzywa
jakość
reklama rtv
Warszawa
1790658
NaN
NaN
8
24
R_011
m
zawodowe
24.0
3.0
osiedlowy
Kaufland
owoce i warzywa
skład
gazetka
Włocławek
115561
NaN
7092.0
3
40
R_004
m
podstawowe
NaN
1.0
osiedlowy
Lidl
produkty piekarnicze
jakość
gazetka
Stargard
71464
45000.0
6084.0
8
48
R_003
m
NaN
49.0
1.0
bazarek
Biedronka
produkty piekarnicze
jakość
reklama rtv
Świdnica
60281
50000.0
5160.0
3
52
R_007
m
zawodowe
NaN
5.0
bazarek
Lidl
produkty zbożowe
marka
gazetka
Olsztyn
170904
29000.0
NaN
3
58
R_053
k
NaN
21.0
3.0
osiedlowy
Dino
słodycze
jakość
gazetka
Jaworzno
94731
41000.0
9780.0
3
82
R_104
k
wyższe
77.0
5.0
galeria
Dino
owoce i warzywa
opakowanie
reklama rtv
Nowy Sącz
84270
NaN
12024.0
8
108
R_112
k
wyższe
53.0
5.0
NaN
NaN
słodycze
jakość
nan
Elbląg
119144
54000.0
12936.0
19
109
R_078
k
wyższe
25.0
2.0
supermarket
Netto
słodycze
jakość
nie korzystam
Radomsko
49898
NaN
10692.0
3
113
R_095
k
NaN
75.0
4.0
osiedlowy
Biedronka
mięso i wędliny
jakość
reklama rtv
Słupsk
93460
48000.0
11544.0
19
120
R_118
k
wyższe
63.0
2.0
NaN
NaN
produkty zbożowe
jakość
nan
Ełk
60252
57000.0
14964.0
3
3.3.2. Obsługa brakujących danych (usuwanie [całość, wg progu], imputacja, utworzenie nowej kategorii np. “b.d.”)
W danych znajdują się barkujące dane.Z uwagi na niewielką ilość pozyskanych danych usuwanie takich danych znacznie zmniejszyło by liczbę dostępych danych, dlatego zostanie dokonana imputacja. Natomiast w przypadku wierszy i kolumn , w których znajduje się = >20 % braków o sustaną usuniete, ponieważ tak znaczna ilość braków może nie być losowa, a imputacja mogła by bardzo zniekształcić oryginalny rozkład
Code
baza['dochody'].fillna(baza['dochody'].mean().round(1), inplace=True)baza['wydatki'].fillna(baza['wydatki'].mean().round(1), inplace=True)baza['wiek'].fillna(baza['wiek'].mode()[0], inplace=True)baza['liczba osób w rodzinie'].fillna(baza['liczba osób w rodzinie'].mode()[0], inplace=True)baza['preferowany typ sklepu'].fillna(baza['preferowany typ sklepu'].mode()[0], inplace=True)baza['preferowana marka sklepu'].fillna(baza['preferowana marka sklepu'].mode()[0], inplace=True)baza['wykształcenie'].fillna(baza['wykształcenie'].mode()[0], inplace=True)
Code
braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!
3.4. Obserwacje nietypowe i odstające
3.4.1. Sprawdzenie czy w zmiennych liczbowych wystepują obserwacje odstające ( naturalne, błędy)
Code
def outliers(df, var): data = df[var]def detect_outliers_iqr(data): q1, q3 = np.percentile(data, [25, 75]) iqr = q3 - q1 lower_bound = q1 - (1.5* iqr) upper_bound = q3 + (1.5* iqr) outliers_IQR = [i for i, x inenumerate(data) if x < lower_bound or x > upper_bound]return outliers_IQRdef detect_outliers_mean_std(data): mean = np.mean(data) std = np.std(data) lower_bound = mean - (3* std) upper_bound = mean + (3* std) outliers_mean_std = [i for i, x inenumerate(data) if x < lower_bound or x > upper_bound]return outliers_mean_stddef detect_outliers_zscore(data): threshold =2 z_scores = zscore(data) outliers_zscore = [i for i, z inenumerate(z_scores) ifabs(z) > threshold]return outliers_zscoredef detect_outliers_winsorizing(data): lower_bound, upper_bound = np.percentile(data, [5, 95]) data = np.clip(data, lower_bound, upper_bound) outliers_winsorizing = [i for i, x inenumerate(data) if x < lower_bound or x > upper_bound]return outliers_winsorizing# Reshape the data to a 2D array data = np.array(data).reshape(-1, 1) outliers_IQR = detect_outliers_iqr(data) outliers_mean_std = detect_outliers_mean_std(data) outliers_zscore = detect_outliers_zscore(data) outliers_winsorizing = detect_outliers_winsorizing(data) df = pd.DataFrame(data) df['Odstające_IQR'] = np.where(df.index.isin(outliers_IQR), -1, 1) df['Odstające_mean_std'] = np.where(df.index.isin(outliers_mean_std), -1, 1) df['Odstające_Zscore'] = np.where(df.index.isin(outliers_zscore), -1, 1) df['Odstające_Winsorizing'] = np.where(df.index.isin(outliers_winsorizing), -1, 1)# Dodatkowa kolumna: Czy_Odstające df['Czy_Odstające'] = np.where((df['Odstające_IQR'] ==-1) | (df['Odstające_mean_std'] ==-1) | (df['Odstające_Zscore'] ==-1) | (df['Odstające_Winsorizing'] ==-1) , True, False) df = df.loc[df['Czy_Odstające'] ==True] df = df.rename(columns={0: var}) # Zmiana nazwy kolumny 0 na Nazwa_Zmiennej# Dodaj kolumnę "ile_ident" df['ile_ident'] = df[['Odstające_IQR', 'Odstające_mean_std', 'Odstające_Zscore', 'Odstające_Winsorizing']].apply(lambda row: row.value_counts().get(-1, 0), axis=1)return df
Code
zmienna_liczbowa = ['wiek', 'liczba osób w rodzinie', 'dochody', 'wydatki', 'średnia częstość zakupów w tyg']for kolumna in zmienna_liczbowa: wynik = outliers(baza, kolumna)ifnot wynik.empty:print(f' zmienna "{kolumna}" zawiera obserwacje nietypowe :') display(wynik)else:print (f' zmienna "{kolumna}" nie zawiera obserwacji nietypowych')
zmienna "wiek" nie zawiera obserwacji nietypowych
zmienna "liczba osób w rodzinie" nie zawiera obserwacji nietypowych
zmienna "dochody" zawiera obserwacje nietypowe :
dochody
Odstające_IQR
Odstające_mean_std
Odstające_Zscore
Odstające_Winsorizing
Czy_Odstające
ile_ident
22
26000.0
1
1
-1
1
True
1
62
61000.0
1
1
-1
1
True
1
77
61000.0
1
1
-1
1
True
1
83
60000.0
1
1
-1
1
True
1
zmienna "wydatki" zawiera obserwacje nietypowe :
wydatki
Odstające_IQR
Odstające_mean_std
Odstające_Zscore
Odstające_Winsorizing
Czy_Odstające
ile_ident
33
4200.0
-1
-1
-1
1
True
3
40
6084.0
1
1
-1
1
True
1
48
5160.0
-1
1
-1
1
True
2
49
4200.0
-1
-1
-1
1
True
3
96
14964.0
1
1
-1
1
True
1
115
14964.0
1
1
-1
1
True
1
120
14964.0
1
1
-1
1
True
1
zmienna "średnia częstość zakupów w tyg" nie zawiera obserwacji nietypowych
Dochody_netto–> Wynika, że są to naturalnie obserwacje,zgodne z wiedzą dziedzinową. wydatki_na_żywność –> zidentyfikowane dane są zgodne z dziedzina, ale tak skrajne obserwacje mogą zakłucać rozkład, dlatego zamienimy na NaN, następnie dokonać imputacji
Code
# Numer indeksów, dla których chcemy zmienić wartości na NaNindexes_to_replace = [33,49]for index in indexes_to_replace: baza.at[index, 'wydatki'] = np.nan
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!
3.4.3. Sprawdzenie czy w zmiennych kategorialnych wystepują obserwacje odstające ( naturalne {obserwacje rzadkie, wysoka kardynalnosć}, błędy)
Code
zmienna_tekstowa= ['płeć', 'wykształcenie','preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy','rodzaj promocji']for element in zmienna_tekstowa:print(baza[element].value_counts(normalize =True)*100)print('-'*35)
płeć
k 68.0
m 32.0
Name: proportion, dtype: float64
-----------------------------------
wykształcenie
wyższe 60.0
średnie 20.0
zawodowe 12.8
podstawowe 7.2
Name: proportion, dtype: float64
-----------------------------------
preferowany typ sklepu
supermarket 30.4
osiedlowy 24.0
bazarek 23.2
galeria 22.4
Name: proportion, dtype: float64
-----------------------------------
preferowana marka sklepu
Biedronka 26.4
Lidl 16.8
Kaufland 11.2
Netto 11.2
Żabka 9.6
Aldi 8.0
Dino 4.8
Carrefour 4.8
Polomarket 1.6
Intermache 1.6
Społem 1.6
Groszek 0.8
Lewiatan 0.8
Jeżyk 0.8
Name: proportion, dtype: float64
-----------------------------------
preferowanay towar
produkty piekarnicze 18.4
owoce i warzywa 17.6
mięso i wędliny 16.0
produkty mleczne 15.2
napoje 14.4
słodycze 9.6
produkty zbożowe 8.8
Name: proportion, dtype: float64
-----------------------------------
czynnik zakupowy
jakość 37.6
cena 30.4
marka 16.8
skład 5.6
dostępność 4.0
lokalność 1.6
opakowanie 1.6
preferencje kulinarne 1.6
opinie 0.8
Name: proportion, dtype: float64
-----------------------------------
rodzaj promocji
gazetka 36.0
reklama rtv 27.2
nie korzystam 15.2
karta 11.2
sugestia kasjera 3.2
e-mail 2.4
aplikacja 1.6
nan 1.6
sms 1.6
Name: proportion, dtype: float64
-----------------------------------
3.4.4. Obsługa obserwacji odstających (usuwanie, zamiana na NaN, utworzenie nowej kategorii, przypisanie do nowych kategorii zgodnie wiedzą dziedzinową)
Code
# preferowana marka sklepu # rzadkie i wysoka kardynalnosć - zredukować liczbę poziomów utworzyć osobną grupę "inne" z poziomami poniżej 5dict_preferowana_marka_sklepu ={'Aldi':'Inna_lokalna','Dino':'Inna_lokalna','Carrefour':'Inna_lokalna','Polomarket':'Inna_lokalna','Społem':'Inna_lokalna','Intermache':'Inna_lokalna','Groszek':'Inna_lokalna','Jeżyk':'Inna_lokalna','Lewiatan':'Inna_lokalna'}baza['preferowana marka sklepu'] = baza['preferowana marka sklepu'].replace(dict_preferowana_marka_sklepu)
zmienna_tekstowa= ['preferowany typ sklepu','preferowana marka sklepu', 'preferowanay towar', 'czynnik zakupowy','rodzaj promocji']for element in zmienna_tekstowa: display(baza[element].value_counts(normalize=True)*100)
zmienna= ['wiek_kat', 'dochody_kat', 'wydatki_kat', 'wielkość miasta']for element in zmienna: display(baza[element].value_counts(normalize=True).round(2)*100)
dochody_kat
niski 29.0
bardzo wysoki 25.0
wysoki 24.0
średni 22.0
Name: proportion, dtype: float64
wydatki_kat
niski 26.0
średni 25.0
wysoki 25.0
bardzo wysoki 25.0
Name: proportion, dtype: float64
wielkość miasta
51 tys.-200 tys. 72.0
201 tys.-500 tys. 19.0
pow 500 tys. 6.0
<50 tys. 3.0
Name: proportion, dtype: float64
Code
braki_sprawdzenie(baza)
Analiza brakujących danych:
=============================================
W tabeli nie stwierdzono brakujących danych!
Code
baza.to_excel('baza.xlsx', index=False)
Code
import ipywidgets as widgetsfrom IPython.display import displayimport altair as altbaza# Install matplotlib if not already installed%pip install matplotlibimport matplotlib.pyplot as plt# Create a dropdown widget for selecting a columncolumn_dropdown = widgets.Dropdown( options=baza.select_dtypes(include=['float64', 'int64']).columns.tolist(), description='Kolumna:', disabled=False,)# # Create an integer slider widget for selecting the number of bins# bins_slider = widgets.IntSlider(# value=10,# min=1,# max=50,# step=1,# description='Bins:',# disabled=False,# )# # Function to update the histogram based on selected column and bins# def update_histogram(column, bins):# plt.figure(figsize=(10, 6))# baza[column].plot(kind='hist', bins=bins, edgecolor='black')# plt.title(f'Histogram dla kolumny: {column}')# plt.xlabel(column)# plt.ylabel('Częstotliwość')# plt.grid(True)# plt.show()# # Create an interactive output widget# output = widgets.interactive_output(update_histogram, {'column': column_dropdown, 'bins': bins_slider})# # Display the widgets and output# display(column_dropdown, bins_slider, output)# Function to update the histogram using Altairdef update_histogram_altair(column, bins): chart = alt.Chart(baza).mark_bar().encode( alt.X(column, bin=alt.Bin(maxbins=bins), title=column), alt.Y('count()', title='Częstotliwość') ).properties( title=f'Histogram dla kolumny: {column}', width=600, height=400 ) display(chart)# Create an interactive output widget for Altairoutput_altair = widgets.interactive_output(update_histogram_altair, {'column': column_dropdown, 'bins': bins_slider})# Display the widgets and output for Altairdisplay(column_dropdown, bins_slider, output_altair)
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: matplotlib in /usr/lib/python3/dist-packages (3.6.3)
Note: you may need to restart the kernel to use updated packages.